var userFile = require("./User.js");
var User = userFile.User;
var gameBoardsFile = require("./GameBoards.js");
var GameBoards = gameBoardsFile.GameBoards;

function GameEngine(gameBoard) {
    var r, c;
    this.BOT_COURSE_CHANGE = 4;
    this.BOT_RANDOM_MOVE = 2;
    this.HUNTER_INTERVAL = 75;
    this.ALIEN_INTERVAL = 50;
    this.ALIEN_DAMAGE = 2;
    this.BACKGROUND = 0;
    this.BLOCK = 1;
    this.BOTH_SPAWN = 2;
    this.ALIEN_SPAWN = 3;
    this.HUNTER_SPAWN = 4;
    this.alienMoveIntervalId = null;
    this.hunterMoveIntervalId = null;
    this.botMoveIntervalId = null;
    this.gameBoardUpdated = null;
    this.playerDied = null;
    this.gameOver = null;
    this.gameHasStarted = false;
    this.gameHasEnded = false;
    this.gameLocked = false;
    this.hostId = undefined;
    this.moving = false;

    this.users = new Object();
    this.width = gameBoard[0].length;
    this.height = gameBoard.length;
    this.oldGameBoard = gameBoard;

    this.newGameBoard = new Array();

    for (r = 0; r < this.height; r++) {
        this.newGameBoard[r] = new Array();
        for (c = 0; c < this.width; c++) {
            this.newGameBoard[r][c] = this.oldGameBoard[r][c];
        }
    }
}

GameEngine.prototype = (function () {
    var getHunters = function () {
        var hunters = new Array();

        for (var id in this.users) {
            if (!this.users[id].alien) {
                hunters.push(this.users[id]);
            }
        }

        return hunters;
    };

    var getAliens = function () {
        var aliens = new Array();

        for (var id in this.users) {
            if (this.users[id].alien) {
                aliens.push(this.users[id]);
            }
        }

        return aliens;
    };

    var getBots = function () {
        var bots = new Array();

        for (var id in this.users) {
            if (this.users[id].bot) {
                bots.push(this.users[id]);
            }
        }

        return bots;
    };

    var cycleGameBoards = function () {
        var r, c;

        for (r = 0; r < this.height; r++) {
            for (c = 0; c < this.width; c++) {
                this.oldGameBoard[r][c] = this.newGameBoard[r][c];
            }
        }
    };

    var getDistance = function (player1, player2) {
        var distance = Math.abs(player1.row - player2.row) + Math.abs(player1.column - player2.column);
    };

    var queueBotMoves = function () {
        var bots = getBots.call(this);
        var hunters = getHunters.call(this);
        var i, j;

        for (i = 0; i < bots.length; i++) {
            if (bots[i].botRandomCount > 0) {
                bots[i].setQueuedMove(Math.floor(Math.random() * 4) + 1);
                bots[i].botRandomCount--;
            }
            else {
                var trackingDistance = Number.MAX_VALUE;
                var nearestDistance = Number.MAX_VALUE;
                var nearestId;
                var distance;

                //find out who to track
                for (j = 0; j < hunters.length; j++) {
                    distance = getDistance(hunters[j], bots[i]);
                    if (hunters[j].id == bots[i].trackingId) {
                        trackingDistance = distance;
                    }
                    if (distance < nearestDistance) {
                        nearestDistance = distance;
                        nearestId = hunters[j].id;
                    }

                    //change tracking
                    if (!bots[i].trackingId || nearestDistance < (trackingDistance - this.BOT_COURSE_CHANGE)) {
                        bots[i].trackingId = hunters[j].id;
                    }
                }

                //move towards the prey we are tracking
                var prey = this.users[bots[i].trackingId];
                var colDistance = bots[i].column - prey.column;
                var rowDistance = bots[i].row - prey.row;

                var moves = new Array();

                //try to move horizontally first
                if (Math.abs(colDistance) >= Math.abs(rowDistance)) {
                    if (colDistance > 0) {
                        moves.push(User.MOVE_LEFT);
                    }
                    else if (colDistance < 0) {
                        moves.push(User.MOVE_RIGHT);
                    }

                    if (rowDistance > 0) {
                        moves.push(User.MOVE_UP);
                    }
                    else if (rowDistance < 0) {
                        moves.push(User.MOVE_DOWN);
                    }
                }
                //try to move vertically first
                else {
                    if (rowDistance > 0) {
                        moves.push(User.MOVE_UP);
                    }
                    else if (rowDistance < 0) {
                        moves.push(User.MOVE_DOWN);
                    }

                    if (colDistance > 0) {
                        moves.push(User.MOVE_LEFT);
                    }
                    else if (colDistance < 0) {
                        moves.push(User.MOVE_RIGHT);
                    }
                }

                //see if we can move
                var newPlace;
                var canMove = false;

                for (var k = 0; k < moves.length; k++) {
                    switch (moves[k]) {
                        case User.MOVE_UP:
                            if (bots[i].row > 0) {
                                newPlace = this.newGameBoard[bots[i].row - 1][bots[i].column];
                            }
                            break;
                        case User.MOVE_RIGHT:
                            if (bots[i].column < this.width - 1) {
                                newPlace = this.newGameBoard[bots[i].row][bots[i].column + 1];
                            }
                            break;
                        case User.MOVE_DOWN:
                            if (bots[i].row < this.height - 1) {
                                newPlace = this.newGameBoard[bots[i].row + 1][bots[i].column];
                            }
                            break;
                        case User.MOVE_LEFT:
                            if (bots[i].column > 0) {
                                newPlace = this.newGameBoard[bots[i].row][bots[i].column - 1];
                            }
                            break;
                    }

                    if (newPlace != undefined && (newPlace == this.BACKGROUND || newPlace >= User.HUNTER_START)) {
                        canMove = true;
                        bots[i].setQueuedMove(moves[k]);
                        break;
                    }
                }

                if (!canMove) {
                    if (bots[i].cantMoveRow == bots[i].row && bots[i].cantMoveColumn == bots[i].column) {
                        bots[i].cantMoveCount++;
                        //console.log(bots[i].cantMoveCount);
                    }
                    else {
                        bots[i].cantMoveRow = bots[i].row;
                        bots[i].cantMoveColumn = bots[i].column;
                        bots[i].cantMoveCount = 1;
                    }

                    bots[i].trackingId = null;
                    bots[i].botRandomCount = Math.pow(this.BOT_RANDOM_MOVE, bots[i].cantMoveCount);
                }
            }
        }
    };

    var moveHunters = function () {
        var hunters = getHunters.call(this);
        if (hunters.length > 0) {
            movePlayers.call(this, hunters);
            var playerChanges = false;
            updateGameBoard.call(this, playerChanges, false);
        }
        checkForGameOver.call(this);
    };

    var moveAliens = function () {
        var aliens = getAliens.call(this);
        if (aliens.length > 0) {
            movePlayers.call(this, aliens);
            var playerChanges = checkForTrappedAliens.call(this, aliens);
            updateGameBoard.call(this, playerChanges, false);
        }
        checkForGameOver.call(this);
    };

    var moveBots = function () {
        queueBotMoves.call(this);
        var bots = getBots.call(this);
        if (bots.length > 0) {
            movePlayers.call(this, bots);
            var playerChanges = checkForTrappedAliens.call(this, bots);
            updateGameBoard.call(this, playerChanges, true);
        }
        checkForGameOver.call(this);
    };

    var checkForGameOver = function () {
        var aliens = false;
        var hunters = false;

        for (var id in this.users) {
            aliens = aliens || this.users[id].alien;
            hunters = hunters || !this.users[id].alien;
            if (aliens && hunters) {
                break;
            }
        }

        if (!aliens || !hunters) {
            this.gameHasEnded = true;
            clearInterval(this.alienMoveIntervalId);
            clearInterval(this.hunterMoveIntervalId);
            clearInterval(this.botMoveIntervalId);
            this.gameOver(aliens);
        }
    };

    var checkForTrappedAliens = function (aliens) {
        var playerChanges = false;
        var i;
        for (i = 0; i < aliens.length; i++) {
            var up = { r: aliens[i].row - 1, c: aliens[i].column };
            var right = { r: aliens[i].row, c: aliens[i].column + 1 };
            var down = { r: aliens[i].row + 1, c: aliens[i].column };
            var left = { r: aliens[i].row, c: aliens[i].column - 1 };

            if ((up.r < 0 || this.newGameBoard[up.r][up.c] == this.BLOCK)
                    && (right.c >= this.width || this.newGameBoard[right.r][right.c] == this.BLOCK)
                    && (down.r >= this.height || this.newGameBoard[down.r][down.c] == this.BLOCK)
                    && (left.c < 0 || this.newGameBoard[left.r][left.c] == this.BLOCK)) {
                var newHealth = aliens[i].health - this.ALIEN_DAMAGE;
                //console.log(newHealth);
                if (newHealth <= 0) {
                    removePlayer.call(this, aliens[i].id);
                    this.playerDied(aliens[i].id);
                }
                else {
                    playerChanges = true;
                    aliens[i].setHealth(newHealth);
                }
            }
            else if (aliens[i].health != 100) {
                aliens[i].setHealth(100);
            }
        }

        return playerChanges;
    };

    var movePlayers = function (players) {
        var i;
        for (i = 0; i < players.length; i++) {
            movePlayer.call(this, players[i]);
        }
    };

    var movePlayer = function (user) {
        var newPlace = undefined;

        switch (user.queuedMove) {
            case User.NO_MOVE:
                break;
            case User.MOVE_UP:
                if (user.row > 0) {
                    newPlace = this.newGameBoard[user.row - 1][user.column];
                }
                break;
            case User.MOVE_RIGHT:
                if (user.column < this.width - 1) {
                    newPlace = this.newGameBoard[user.row][user.column + 1];
                }
                break;
            case User.MOVE_DOWN:
                if (user.row < this.height - 1) {
                    newPlace = this.newGameBoard[user.row + 1][user.column];
                }
                break;
            case User.MOVE_LEFT:
                if (user.column > 0) {
                    newPlace = this.newGameBoard[user.row][user.column - 1];
                }
                break;
        }

        if (newPlace != undefined) {
            if (newPlace == this.BLOCK) {
                pushBlocks.call(this, user);
            }
            else if (newPlace === this.BACKGROUND) {
                this.newGameBoard[user.row][user.column] = this.BACKGROUND;
                user.move();
                this.newGameBoard[user.row][user.column] = user.id;
            }
            else {
                var userSign = user.id > 0;
                var placeSign = newPlace > 0;
                if (userSign != placeSign) {
                    if (user.id <= User.ALIEN_START) {
                        removePlayer.call(this, newPlace);
                        this.playerDied(newPlace);
                        this.newGameBoard[user.row][user.column] = this.BACKGROUND;
                        user.move();
                        this.newGameBoard[user.row][user.column] = user.id;
                    }
                    else if (user.id >= User.HUNTER_START) {
                        removePlayer.call(this, user.id);
                        this.playerDied(user.id);
                    }
                }
            }
        }

        user.clearQueuedMove();
    };

    var pushBlocks = function (user) {
        var rowDirection = 0;
        var columnDirection = 0;

        if (user.canPushBlocks()) {
            switch (user.queuedMove) {
                case User.MOVE_UP:
                    rowDirection--;
                    break;
                case User.MOVE_RIGHT:
                    columnDirection++;
                    break;
                case User.MOVE_DOWN:
                    rowDirection++;
                    break;
                case User.MOVE_LEFT:
                    columnDirection--;
                    break;
            }

            var row = user.row + rowDirection;
            var column = user.column + columnDirection;
            var boardPiece = this.newGameBoard[row][column];
            var newRow = user.row + rowDirection;
            var newColumn = user.column + columnDirection;

            while (boardPiece == this.BLOCK && newRow >= 0 && newRow < this.height && newColumn >= 0 && newColumn < this.width) {
                row = newRow;
                column = newColumn;
                newRow += rowDirection;
                newColumn += columnDirection;
                boardPiece = this.newGameBoard[row][column];
            }

            if (boardPiece == this.BACKGROUND) {
                this.newGameBoard[user.row][user.column] = this.BACKGROUND;
                user.move();
                this.newGameBoard[user.row][user.column] = user.id;
                this.newGameBoard[row][column] = this.BLOCK;
            }
        }
    };

    var spawnPlayer = function (user) {
        var row = 0;
        var column = 0;

        do {
            row = Math.floor(Math.random() * this.height);
            column = Math.floor(Math.random() * this.width);
        } while (!canSpawn.call(this, user, this.newGameBoard[row][column]));

        user.setPosition(row, column);
        this.newGameBoard[row][column] = user.id;
    };

    var canSpawn = function (user, place) {
        if (this.gameHasStarted) {
            return place == this.BACKGROUND;
        }
        else {
            if (user.id <= User.ALIEN_START) {
                return (place == this.BOTH_SPAWN) || (place == this.ALIEN_SPAWN);
            }
            else {
                return (place == this.BOTH_SPAWN) || (place == this.HUNTER_SPAWN);
            }
        }
    };

    var removePlayer = function (playerId) {
        var playerRemoved = false;
        var r, c;

        if (this.users[playerId]) {
            delete this.users[playerId];

            for (r = 0; r < this.height; r++) {
                for (c = 0; c < this.width; c++) {
                    if (this.newGameBoard[r][c] == playerId) {
                        this.newGameBoard[r][c] = this.BACKGROUND;
                        playerRemoved = true;
                    }
                }
            }
        }

        return playerRemoved;
    };

    var removeAllSpawnPoints = function () {
        var r, c;

        for (r = 0; r < this.height; r++) {
            for (c = 0; c < this.width; c++) {
                if (this.newGameBoard[r][c] == this.ALIEN_SPAWN || this.newGameBoard[r][c] == this.HUNTER_SPAWN || this.newGameBoard[r][c] == this.BOTH_SPAWN) {
                    this.newGameBoard[r][c] = this.BACKGROUND;
                }
            }
        }
    };

    var getUpdates = function () {
        var updates = new Array();

        var r, c;

        for (r = 0; r < this.height; r++) {
            for (c = 0; c < this.width; c++) {
                if (this.newGameBoard[r][c] != this.oldGameBoard[r][c]) {
                    updates.push({ r: r, c: c, id: this.newGameBoard[r][c] });
                }
            }
        }

        return updates;
    };

    var updateGameBoard = function (playerChanges, updateFPS) {
        var updates = getUpdates.call(this);
        cycleGameBoards.call(this);
        this.gameBoardUpdated(updates, playerChanges, updateFPS);
    };

    var canJoin = function (user) {
        var canJoin = true;
        for (var id in this.users) {
            if (this.users[id].userName == user.userName) {
                canJoin = false;
                break;
            }
        }
        return canJoin;
    };

    var changeGameBoard = function (gameBoard) {
        this.oldGameBoard = gameBoard;
        this.newGameBoard = new Array();

        for (r = 0; r < this.height; r++) {
            this.newGameBoard[r] = new Array();
            for (c = 0; c < this.width; c++) {
                this.newGameBoard[r][c] = this.oldGameBoard[r][c];
            }
        }
    };

    return {
        constructor: GameEngine,

        startGame: function (alienInterval, hunterInterval, botInterval) {
            var i;
            var user;

            for (var id in this.users) {
                spawnPlayer.call(this, this.users[id]);
            }

            removeAllSpawnPoints.call(this);

            this.ALIEN_INTERVAL = alienInterval;
            this.HUNTER_INTERVAL = hunterInterval;
            this.BOT_INTERVAL = botInterval;
            this.ALIEN_DAMAGE = this.ALIEN_INTERVAL / 25;
            this.gameHasStarted = true;

            var that = this;

            (function moveHuntersRepeated() {
                if (!that.gameHasEnded) {
                    moveHunters.call(that);
                    hunterMoveIntervalId = setTimeout(moveHuntersRepeated, that.HUNTER_INTERVAL);
                }
            })();

            (function moveAliensRepeated() {
                if (!that.gameHasEnded) {
                    moveAliens.call(that);
                    alienMoveIntervalId = setTimeout(moveAliensRepeated, that.ALIEN_INTERVAL);
                }
            })();

            if (getBots.call(this).length > 0) {
                (function moveBotsRepeated() {
                    if (!that.gameHasEnded) {
                        moveBots.call(that);
                        botMoveIntervalId = setTimeout(moveBotsRepeated, that.BOT_INTERVAL);
                    }
                })();
            }
        },

        addUser: function (user) {
            if (!this.gameLocked && canJoin.call(this, user)) {
                user.setId();
                this.users[user.id] = user;
                if (!this.hostId) {
                    this.hostId = user.id;
                }
                if (this.gameHasStarted) {
                    spawnPlayer.call(this, user);
                }
                return user;
            }
            else {
                return null;
            }
        },

        removeUser: function (userId) {
            var playerRemoved = removePlayer.call(this, userId);

            //elect a new host
            if (userId == this.hostId) {
                var loginTime = Number.MAX_VALUE;
                this.hostId = undefined;
                for (var id in this.users) {
                    if (!!this.users[id].bot && this.users[id].loginTime < loginTime) {
                        loginTime = this.users[id].loginTime;
                        this.hostId = id;
                    }
                }
            }

            return playerRemoved;
        },

        getUpdatesForNewPlayer: function () {
            var updates = new Array();

            var r, c;

            for (r = 0; r < this.height; r++) {
                for (c = 0; c < this.width; c++) {
                    updates.push({ r: r, c: c, id: this.newGameBoard[r][c] });
                }
            }

            return updates;
        },

        getUser: function (id) {
            return this.users[id];
        },

        getUsers: function () {
            return this.users;
        },

        getNumberOfUsers: function () {
            return Object.keys(this.users).length;
        },

        getHost: function () {
            return this.users[this.hostId];
        },

        generateBoard: function (fillFactor) {
            changeGameBoard.call(this, GameBoards.getRandomBoard(fillFactor));
        },

        generateLevel: function (levelNumber) {
            changeGameBoard.call(this, GameBoards.getLevel(levelNumber));
        },

        lockGame: function (fillFactor) {
            this.gameLocked = true;
        },

        unlockGame: function (levelNumber) {
            this.gameLocked = false;
        },

        isGameOpen: function () {
            return !this.gameLocked;
        }
    };
})();

exports.GameEngine = GameEngine;